﻿using System;
using RayDen.Library.Components.Surface;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.Library.Entity.Scene;
using RayDen.RayEngine.Core.Types;

namespace RayDen.RayEngine.Core.Surface
{
    public class MetalBrdf : BaseBxdf
    {
        public MetalBrdf()
        {
            
        }
        public MetalBrdf(ref RayHit rh, ref RayData ray, ref Normal ng, ref Normal ns, ref UV texCoord, MaterialInfo mi, SurfaceTextureInfo texData, bool fromLight)
            : base(ref rh, ref ray, ref ng, ref ns, ref texCoord, mi, texData, fromLight)
        {
        }

        public override bool IsDelta
        {
            get { return true; }
        }

        protected override RgbSpectrum Sample(ref HitPointInfo HitPoint, ref Vector localFixedDir, out Vector localSampledDir, float u0, float u1, out float pdfW, out float absCosDir, out BsdfEvent _event)
        {
            _event = this.Type.TypeToEvent();

            localSampledDir = new Vector(-localFixedDir.x, -localFixedDir.y, localFixedDir.z);
            pdfW = 1f;

            absCosDir = Math.Abs(localSampledDir.z);
            var kr = (RgbSpectrum)TexData.Specular;
            float e = 1f / (Math.Max(TexData.Exponent, 0f) + 1f);
            localSampledDir = GlossyReflection(ref localFixedDir, e, u0, u1);

            if (localSampledDir.z * localFixedDir.z > 0f)
            {
                _event = BsdfEvent.Specular | BsdfEvent.Reflect;
                pdfW = 1f;
                absCosDir = Math.Abs(localSampledDir.z);
                // The absCosSampledDir is used to compensate the other one used inside the integrator
                return kr / (absCosDir);
            }
            else
                return RgbSpectrum.ZeroSpectrum();
        }

        private Vector GlossyReflection(ref Vector localFixedDir, float exponent, float u0, float u1)
        {
            bool into = (localFixedDir.z > 0f);
            Vector shadeN = new Vector(0f, 0f, into ? 1f : -1f);

            float phi = 2f * MathLab.M_PI * u0;
            float cosTheta = (float)MathLab.Pow(1f - u1, exponent);
            float sinTheta = MathLab.Sqrt(Math.Max(0f, 1f - cosTheta * cosTheta));
            float x = MathLab.Cos(phi) * sinTheta;
            float y = MathLab.Sin(phi) * sinTheta;
            float z = cosTheta;

            Vector dir = -localFixedDir;
            float dp = Vector.Dot(ref shadeN, ref dir);
            Vector w = dir - (2f * dp) * shadeN;

            Vector u = Vector.Normalize(Vector.Cross((Math.Abs(shadeN.x) > .1f) ? new Vector(0f, 1f, 0f) : new Vector(1f, 0f, 0f),w));
            Vector v = Vector.Cross(ref w, ref u);

            return x * u + y * v + z * w;
        }

        protected override RgbSpectrumInfo Evaluate(ref HitPointInfo HitPoint, ref Vector localLightDir, ref Vector localEyeDir, out BsdfEvent _event, out float directPdfW, out float reversePdfW)
        {
            _event = this.Type.TypeToEvent();
            directPdfW = 0f;
            reversePdfW = 0f;
            return RgbSpectrumInfo.Black;
        }

        public override BrdfType Type
        {
            get { return BrdfType.Specular | BrdfType.Reflection; }
        }

        public override bool IsLightSource()
        {
            return false;
        }

        public override bool IsDiffuse()
        {
            return false;
        }

        public override bool IsRefractive()
        {
            return false;
        }

        public override bool IsSpecular()
        {
            return true;
        }
    }
}